// Classes downloaded from the article "Parsing HTML in Microsoft C#"
// by Jeff Heaton.
//
// http://www.developer.com/net/csharp/article.php/2230091

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;

namespace HTML
{
    /// <summary>
    /// HTML parser.
    ///
    /// This source code may be used freely under the
    /// Limited GNU Public License(LGPL).
    ///
    /// Written by Jeff Heaton (http://www.jeffheaton.com)
    /// </summary>
    public class ParseHTML : Parse
    {
        /// <summary>
        /// Gets the current tag
        /// </summary>
        /// <returns>Current tag</returns>
        public AttributeList GetTag()
        {
            AttributeList tag = new AttributeList();
            tag.Name = m_tag;

            foreach (Attribute x in List)
            {
                tag.Add((Attribute)x.Clone());
            }

            return tag;
        }

        internal void ParseTag()
        {
            m_tag = "";
            Clear();

            // Is it a comment?
            if ((GetCurrentChar() == '!') &&
              (GetCurrentChar(1) == '-') &&
              (GetCurrentChar(2) == '-'))
            {
                while (!Eof())
                {
                    if ((GetCurrentChar() == '-') &&
                      (GetCurrentChar(1) == '-') &&
                      (GetCurrentChar(2) == '>'))
                        break;
                    if (GetCurrentChar() != '\r')
                        m_tag += GetCurrentChar();
                    Advance();
                }
                m_tag += "--";
                Advance();
                Advance();
                Advance();
                ParseDelim = (char)0;
                return;
            }

            // Find the tag name
            while (!Eof())
            {
                if (IsWhiteSpace(GetCurrentChar()) ||
                                 (GetCurrentChar() == '>'))
                    break;
                m_tag += GetCurrentChar();
                Advance();
            }

            EatWhiteSpace();

            // Get the attributes
            while (GetCurrentChar() != '>')
            {
                ParseName = "";
                ParseValue = "";
                ParseDelim = (char)0;

                ParseAttributeName();

                if (GetCurrentChar() == '>')
                {
                    AddAttribute();
                    break;
                }

                // Get the value(if any)
                ParseAttributeValue();
                AddAttribute();
            }
            Advance();
        }

        /// <summary>
        /// Parse the current HTML input
        /// </summary>
        /// <returns>0 if the input is a tag, the current character otherwise
        /// </returns>
        public char Parse()
        {
            if (GetCurrentChar() == '<')
            {
                Advance();

                char ch = GetCurrentChar();
                if ((ch >= 'A') && (ch <= 'Z') || (ch >= 'a') && (ch <= 'z') || (ch == '!') || (ch == '/'))
                {
                    ParseTag();
                    return (char)0;
                }

                else return (AdvanceCurrentChar());
            }
            else return (AdvanceCurrentChar());
        }

        /// <summary>
        /// Gets the String content of a tag.
        /// </summary>
        /// <param name="parse">The ParseHTML object that represents the page.
        /// </param>
        /// <param name="tag">The start tag.</param>
        /// <returns>String content of tag.</returns>
        public static String GetTag(ParseHTML parse, AttributeList tag)
        {
            String tagEnd = "/" + tag.Name;
            StringBuilder sb = new StringBuilder();
            while (!parse.Eof())
            {
                char ch = parse.Parse();
                if (ch == 0)
                {
                    tag = parse.GetTag();

                    if (tag.Name == "br")
                    {
                        sb.Append("\r\n");
                    }
                    else if (tag.Name == tagEnd)
                    {
                        return ParseHTML.HTMLDecode(sb.ToString());
                    }
                }
                else
                {
                    sb.Append(ch);
                }
            }
            return ParseHTML.HTMLDecode(sb.ToString());
        }

        /// <summary>
        /// Helper method do decode HTML-encoded strings.<br/><br/>
        /// We define this method since HttpUtility.HtmlDecode adds many
        /// useless dependencies in the Mobile version.
        /// </summary>
        /// <param name="html">HTML-encoded string to decode.</param>
        /// <returns>HTML-decoded version of html.</returns>
        public static String HTMLDecode(String html)
        {
            StringBuilder result = new StringBuilder();
            for (int i = 0; i < html.Length; i++)
            {
                if (html[i] == '&')
                {
                    int end = html.IndexOf(';', i);
                    if (end != -1)
                    {
                        try
                        {
                            String codeString = html.Substring(i + 1, end - i - 1);
                            if (codeString[0] == '#')
                            {
                                int code;
                                if (codeString[1] == 'x' || codeString[1] == 'X')
                                {
                                    code = int.Parse(codeString.Substring(2), NumberStyles.AllowHexSpecifier);
                                }
                                else
                                {
                                    code = int.Parse(codeString.Substring(1));
                                }
                                byte[] utf8 = BitConverter.GetBytes(code);
                                char[] chars = Encoding.UTF8.GetChars(utf8);
                                result.Append(chars[0]);
                            }
                            else if (codeString == "amp")
                            {
                                result.Append('&');
                            }
                            else if (codeString == "lt")
                            {
                                result.Append('<');
                            }
                            else if (codeString == "gt")
                            {
                                result.Append('>');
                            }
                            else if (codeString == "quot")
                            {
                                result.Append('"');
                            }
                            else if (codeString == "apos")
                            {
                                result.Append('\'');
                            }
                            else
                            {
                                throw new Exception("Unknown HTML codeString: \"" + codeString + "\"");
                            }

                            i = end + 1;
                        }
                        catch
                        {
                            // Invalid HTML code string: ignore exception and append
                        }
                    }
                }

                // if html[i] != '&' or some exception thrown
                if (i < html.Length)
                {
                    result.Append(html[i]);
                }
            }
            return result.ToString();
        }

        /// <summary>
        /// Utility function that gets all the form inputs in an HTML document.
        /// </summary>
        /// <param name="html">HTML document</param>
        /// <returns>Map with as key the inputs and as value the corresponding value.</returns>
        public static Dictionary<String, String> GetInputs(String html)
        {
            Dictionary<String, String> result = new Dictionary<String, String>();
            Regex inputMatcher = new Regex("<(input|INPUT)[^>]*>", RegexOptions.IgnoreCase | RegexOptions.Multiline);
            Regex inputNameMatcher = new Regex("name=(\".*?\"|[^ ]*)", RegexOptions.IgnoreCase | RegexOptions.Multiline);
            Regex inputValueMatcher = new Regex("value=(\".*?\"|[^ ]*)", RegexOptions.IgnoreCase | RegexOptions.Multiline);
            foreach (Match inputMatch in inputMatcher.Matches(html.Replace('\n', ' ').Replace('\r', ' ')))
            {
                if (inputNameMatcher.IsMatch(inputMatch.Value))
                {
                    Match nameMatch = inputNameMatcher.Match(inputMatch.Value);
                    IEnumerator nameMatchGroup = nameMatch.Groups.GetEnumerator();
                    nameMatchGroup.MoveNext();
                    nameMatchGroup.MoveNext();
                    String name = ((Group)nameMatchGroup.Current).Value.Replace("\"", "");

                    if (inputValueMatcher.IsMatch(inputMatch.Value))
                    {
                        Match valueMatch = inputValueMatcher.Match(inputMatch.Value);
                        IEnumerator valueMatchGroup = valueMatch.Groups.GetEnumerator();
                        valueMatchGroup.MoveNext();
                        valueMatchGroup.MoveNext();
                        String value = Uri.UnescapeDataString(
                            ((Group)valueMatchGroup.Current).Value.Replace("\"", ""));

                        // Don't use Add but [] in order to save the last value only
                        result[name] = value;
                    }
                }
            }
            return result;
        }
    }
}
